/*
*	Copyright (C) 2014  Julio Montes, Mario Carrillo
*	
*	Julio Montes e-mail: imc.coder@gmail.com 
*
*	Mario Carrillo e-mail: mario_90ago@live.com.mx
*
*	BSD 3-Clause License
*/

#include "sdk/include/filecfg.h"

#include "sdk/include/util.h"
#include "sdk/include/path.h"

#include "tinyxml2.h"
using namespace tinyxml2;

#include <wx/tokenzr.h>

namespace sdk
{
    bool FileConfig::LoadFile( const wxString &pathFile, bool create )
    {
        m_fileName = pathFile;
        if( wxFileExists( pathFile ) )
        {
            if( m_file.LoadFile( pathFile.char_str() ) != XML_NO_ERROR )
            {
                const wxString error( wxT( "Cannot load the file: " ) + pathFile );
                const wxString details( wxT( "File syntax is incorrect" ) );
                const wxString help( wxT( "1. .Check the following lines:\n" ) +
                        wxString::FromUTF8( m_file.GetErrorStr1() ) + wxT( "\n\n" ) + wxString::FromUTF8( m_file.GetErrorStr2() ) );
                sdk::errorDialog( wxT( "Load File config..." ), error, details, help );
                m_file.InsertFirstChild( m_file.NewDeclaration() );

                return false;
            }
        }
        else
        {
            if( !create )
            {
                const wxString error( wxT( "File not found: " ) + pathFile );
                const wxString details( wxT( "File not found" ) );
                const wxString help( wxT( "1. Create the file: " ) + pathFile );
                sdk::errorDialog( wxT( "Load File config..." ), error, details, help );

                return false;
            }
            else
            {
                if( !sdk::mkFile( pathFile ) )
                    return false;

                m_file.InsertFirstChild( m_file.NewDeclaration() );
                m_file.SaveFile( pathFile.char_str() );
            }
        }

        return true;
    }

    const wxString &FileConfig::GetFileName() const
    {
        return m_fileName;
    }

    bool FileConfig::Save()
    {
        return !m_file.SaveFile( m_fileName.char_str() );
    }






    XMLElement *FileConfig::FindElement( const wxString &element, size_t match )
    {
        wxStringTokenizer st( element, wxT( "/" ), wxTOKEN_STRTOK );

        XMLNode *current = &m_file;

        while( st.HasMoreTokens() )
        {
            wxString token( st.GetNextToken() );
            XMLElement *tmp = current->FirstChildElement( token.char_str() );

            if( tmp != NULL )
                current = tmp;

            else
                return NULL;

        }

        for( size_t i=1; i<match && current != NULL; ++i )
            current = current->NextSiblingElement( current->Value() );

        if( current != NULL )
            return current->ToElement();

        return NULL;
    }

    const XMLElement *FileConfig::FindElement( const wxString &element, size_t match ) const
    {
        wxStringTokenizer st( element, wxT( "/" ), wxTOKEN_STRTOK );

        const XMLNode *current = &m_file;

        while( st.HasMoreTokens() )
        {
            wxString token( st.GetNextToken() );
            const XMLElement *tmp = current->FirstChildElement( token.char_str() );

            if( tmp != NULL )
                current = tmp;

            else
                return NULL;

        }

        for( size_t i=1; i<match && current != NULL; ++i )
            current = current->NextSiblingElement( current->Value() );

        if( current != NULL )
            return current->ToElement();

        return NULL;
    }




    XMLElement *FileConfig::InsertElement( const wxString &element )
    {
        wxStringTokenizer st( element, wxT( "/" ), wxTOKEN_STRTOK );

        XMLNode *current = &m_file;

        while( st.HasMoreTokens() )
        {
            wxString token( st.GetNextToken() );
            XMLElement *tmp = current->FirstChildElement( token.char_str() );

            if( tmp != NULL )
                current = tmp;

            else
            {
                LOGD( wxT( "Element not found: '" ) + token + wxT( "' in " ) + element );

                current = current->InsertEndChild( m_file.NewElement( token.char_str() ) );
            }

        }

        return current->ToElement();
    }

    XMLElement *FileConfig::InsertNewElement( const wxString &element, size_t *position )
    {
        wxStringTokenizer st( element, wxT( "/" ), wxTOKEN_STRTOK );

        XMLNode *current = &m_file;

        while( st.HasMoreTokens() )
        {
            wxString token( st.GetNextToken() );

            if( st.HasMoreTokens() )
            {
                XMLElement *tmp = current->FirstChildElement( token.char_str() );
                if( tmp != NULL )
                    current = tmp;

                else
                    current = current->InsertEndChild( m_file.NewElement( token.char_str() ) );
            }
            else
            {
                //insert last node
                current = current->InsertEndChild( m_file.NewElement( token.char_str() ) );

                if( position != NULL )
                {
                    XMLNode *temp = current;
                    (*position)=1;
                    while( ( temp = temp->PreviousSiblingElement( token.char_str() ) ) != NULL )
                    {
                        ++(*position);
                    }
                }
            }
        }

        return current->ToElement();
    }



    wxString FileConfig::ReadStringAttribute( const wxString &element, const wxString &attribute, size_t match ) const
    {
        const XMLElement *current( FindElement( element, match ) );

        if( current != NULL )
        {
            const char *attr = current->Attribute( attribute.char_str() );
            if( attr != NULL )
                return wxString::FromUTF8( attr );
            else
            {
                LOGD( wxT( "Attribute not found: '" ) + attribute + wxT( "' in element " ) + element );
            }
        }
        else
        {
            LOGD( wxT( "Element not found: '" ) + element );
        }

        return wxEmptyString;
    }



    wxString FileConfig::ReadString( const wxString &element, const wxString &defaultValue, const wxString &attribute )
    {
        XMLElement *current( InsertElement( element ) );

        const char *attr = current->Attribute( attribute.char_str() );
        if( attr != NULL )
            return wxString::FromUTF8( attr );
        else
        {
            LOGD( wxT( "Attribute not found: '" ) + attribute + wxT( "' in element " ) + element );

            current->SetAttribute( attribute.char_str(), defaultValue.char_str() );
        }

        return defaultValue;
    }







    size_t FileConfig::WriteString( const wxString &element, const wxString &value, const wxString &attribute, size_t match )
    {
        size_t found = match;
        XMLElement *current( FindElement( element, match ) );

        if( current == NULL )
        {
            found = 1;
            current = InsertElement( element );
        }

        current->SetAttribute( attribute.char_str(), value.char_str() );

        return found;
    }


    size_t FileConfig::WriteNewString( const wxString &element, const wxString &value, const wxString &attribute )
    {
        size_t l_position;
        XMLElement *current( InsertNewElement( element, &l_position ) );
        current->SetAttribute( attribute.char_str(), value.char_str() );
        return l_position;
    }


    int FileConfig::WriteNewStringInElement( const wxString &element, size_t match, const wxString &nameNewElement, const wxString &value, const wxString &attribute )
    {
        XMLElement *elementFound = FindElement( element, match );
        if( elementFound != NULL )
        {
            XMLNode *current = elementFound->InsertEndChild( m_file.NewElement( nameNewElement.char_str() ) );
            current->ToElement()->SetAttribute( attribute.char_str(), value.char_str() );

            int position=1;
            while( ( current = current->PreviousSiblingElement( nameNewElement.char_str() ) ) != NULL )
            {
                ++position;
            }

            return position;
        }

        return -1;
    }





    int FileConfig::ReadAttributes( const wxString &element, sdk::vectorAttributesString *vAttributes, size_t match ) const
    {
        const XMLElement *current( FindElement( element, match ) );
        size_t countAttributes = 0;

        if( current != NULL )
        {
            const XMLAttribute *attr = current->FirstAttribute();

            //read attributes
            while( attr != NULL )
            {
                sdk::attributeString string_attr;
                string_attr.name = wxString::FromUTF8( attr->Name() );
                string_attr.value = wxString::FromUTF8( attr->Value() );
                vAttributes->push_back( string_attr );
                attr = attr->Next();
                ++countAttributes;
            }

            return countAttributes;
        }

        return -1;
    }

    int FileConfig::WriteAttributesInElement( const wxString &element, size_t match, const wxString &nameNewElement, const sdk::vectorAttributesString &vAttributes )
    {
        XMLElement *elementFound = FindElement( element, match );
        if( elementFound != NULL )
        {
            XMLNode *current = elementFound->InsertEndChild( m_file.NewElement( nameNewElement.char_str() ) );
            for( size_t i=0; i<vAttributes.size(); ++i )
                current->ToElement()->SetAttribute( vAttributes[i].name.char_str(), vAttributes[i].value.char_str() );

            int position=1;
            while( ( current = current->PreviousSiblingElement( nameNewElement.char_str() ) ) != NULL )
            {
                ++position;
            }

            return position;
        }

        return -1;
    }

    void FileConfig::WriteAttributes( const wxString &element, const sdk::vectorAttributesString &vAttributes )
    {
        XMLElement *current = InsertElement( element );

        for( size_t i=0; i<vAttributes.size(); ++i )
            current->SetAttribute( vAttributes[i].name.char_str(), vAttributes[i].value.char_str() );
    }

    void FileConfig::WriteNewAttributes( const wxString &element, const sdk::vectorAttributesString &vAttributes )
    {
        XMLElement *current = InsertNewElement( element );

        for( size_t i=0; i<vAttributes.size(); ++i )
            current->SetAttribute( vAttributes[i].name.char_str(), vAttributes[i].value.char_str() );
    }




    size_t FileConfig::ReadElements( const wxString &element, sdk::vectorElementsString *vElements ) const
    {
        const XMLElement *current( FindElement( element ) );
        size_t countElements = 0;

        //read elements
        while( current != NULL )
        {
            const XMLAttribute *attr = current->FirstAttribute();
            sdk::elementString l_element;
            l_element.name = wxString::FromUTF8( current->Name() );

            //read attributes
            while( attr != NULL )
            {
                sdk::attributeString string_attr;
                string_attr.name = wxString::FromUTF8( attr->Name() );
                string_attr.value = wxString::FromUTF8( attr->Value() );

                l_element.vectorAttributes.push_back( string_attr );

                attr = attr->Next();
            }

            vElements->push_back( l_element );
            ++countElements;
            current = current->NextSiblingElement( current->Value() );
        }

        return countElements;
    }





    int FileConfig::ReadChildren( const wxString &element, sdk::vectorElementsString *vElements, size_t match ) const
    {
        const XMLElement *current( FindElement( element, match ) );

        if( current != NULL )
        {
            current = current->FirstChildElement();
            size_t countChildren = 0;

            //read children
            while( current != NULL )
            {
                sdk::elementString l_element;
                l_element.name = wxString::FromUTF8( current->Name() );
                const XMLAttribute *attr = current->FirstAttribute();

                //read attributes
                while( attr != NULL )
                {
                    sdk::attributeString string_attr;
                    string_attr.name = wxString::FromUTF8( attr->Name() );
                    string_attr.value = wxString::FromUTF8( attr->Value() );
                    l_element.vectorAttributes.push_back( string_attr );
                    attr = attr->Next();
                }

                vElements->push_back( l_element );
                ++countChildren;

                current = current->NextSiblingElement();
            }

            return countChildren;
        }

        return -1;
    }







    bool FileConfig::DeleteElement( const wxString &element, size_t match )
    {
        XMLElement *current( FindElement( element, match ) );

        if( current != NULL )
        {
            current->Parent()->DeleteChild( current );
            return true;
        }

        return false;
    }

    size_t FileConfig::DeleteAllElements( const wxString &element )
    {
        XMLElement *current( FindElement( element ) );
        size_t i=0;

        while( current != NULL )
        {
            XMLElement *next = current->NextSiblingElement( current->Value() );
            current->Parent()->DeleteChild( current );
            current = next;
            ++i;
        }

        return i;
    }
}
